package com.passion.common.util;

import cn.hutool.core.io.IoUtil;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 类描述：文件处理工具类
 *
 * @author ZhuYin
 * @since 2024年12月15日
 */
public class FileUtil extends cn.hutool.core.io.FileUtil {

    private static final String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    /**
     * 合法的后缀名，后续扩展
     */
    public static String[] ALLOWED_FILE_SUFFIX = new String[]{
            "png", "bmp", "jpg", "jpeg", "pdf",
            "xlsx", "xls", "gif", "svg", "txt",
            "zip", "ppt", "doc", "docx", "html",
            "htm", "ico", "mp3", "mp4", "java",
            "sql", "xml", "js", "py", "php", "vue",
            "sh", "cmd", "py3", "css", "md", "csv",
            "rar", "zip", "json"
    };

    /** 图片文件 */
    public static String[] FILE_SUFFIX_IMAGE = new String[]{"png", "bmp", "jpg", "jpeg", "svg", "gif"};

    /** 代码文件 */
    public static String[] FILE_SUFFIX_CODE = new String[]{"java", "sql", "js", "py", "py3", "php", "vue", "sh", "cmd", "css"};

    /** 常用的office文件 */
    public static String[] FILE_SUFFIX_NORMAL_OFFICE = new String[]{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf", "txt"};

    /**
     * 方法说明：使用windows、非windows都通用的分割符 标准化文件路径
     * 注意：只针对文件系统的路径做处理，网络访问地址暂不考虑
     *
     * @param path 路径
     * @return {@link String } 返回的路径类似为 C:/usr/local 或 /usr/local 这种多系统兼容的形式
     */
    public static String sopPath(String path) {
        if (StringUtils.isBlank(path)) {
            return path;
        }
        // 先将path中的反斜杠换成正斜杠
        path = path.replace("\\", "/");
        // 再将path中所有成对的正斜杠换成单个正斜杠，循环替换，直到path中没有成对的正斜杠为止
        String doubleSlash = "//";
        while (path.contains(doubleSlash)) {
            path = path.replace(doubleSlash, "/");
        }
        return path;
    }

    /**
     * 方法说明：获取文件名，不带后缀
     *
     * @param fileName 带后缀名的文件名
     * @return 返回不带后缀的文件名
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static String getFileName(String fileName) {
        return fileName.substring(0, fileName.lastIndexOf("."));
    }


    /**
     * 方法说明：获取文件扩展名(不带.号)
     *
     * @param fileName 带后缀名的文件名
     * @return 返回文件后缀(小写)
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static String getFileExtension(String fileName) {
        return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
    }

    /**
     * 方法说明：判断文件类型是否合法
     *
     * @param fileExt 文件扩展名
     * @return boolean
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static boolean isFileAllowed(String fileExt) {
        for (String ext : ALLOWED_FILE_SUFFIX) {
            if (ext.equalsIgnoreCase(fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 方法说明：判断文件是否是代码文件
     *
     * @param fileExt 文件扩展名
     * @return boolean
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static boolean isCodeFile(String fileExt) {
        for (String ext : FILE_SUFFIX_CODE) {
            if (ext.equalsIgnoreCase(fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 方法说明：判断文件是否是图片文件
     *
     * @param fileExt 文件扩展名
     * @return boolean
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static boolean isImgFile(String fileExt) {
        for (String ext : FILE_SUFFIX_IMAGE) {
            if (ext.equalsIgnoreCase(fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 方法说明：判断文件是否是office文件
     *
     * @param fileExt 文件扩展名
     * @return boolean
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static boolean isNormalOfficeFile(String fileExt) {
        for (String ext : FILE_SUFFIX_NORMAL_OFFICE) {
            if (ext.equalsIgnoreCase(fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 方法说明：下载文件
     *
     * @param is         输入流
     * @param outputName 下载时输出给前端的文件名
     * @param response   响应对象
     * @author ZhuYin
     * @since 2025年01月06日
     */
    public static void download(InputStream is, String outputName, HttpServletResponse response) throws IOException {
        ServletOutputStream outputStream = response.getOutputStream();
        // 设置文件ContentType类型，这样设置，会自动判断下载文件类型
        response.setContentType("application/x-msdownload");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(outputName, StandardCharsets.UTF_8.name()));
        IoUtil.copy(is, outputStream);
        response.flushBuffer();
        IoUtil.close(outputStream);
        IoUtil.close(is);
    }

    /**
     * 方法说明：计算文件的md5值
     *
     * @param file 文件对象
     * @return 返回文件的md5值
     * @author ZhuYin
     * @since 2025年01月07日
     */
    public static String fileMd5(File file) throws NoSuchAlgorithmException, IOException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        try (InputStream is = Files.newInputStream(file.toPath());
             BufferedInputStream bis = new BufferedInputStream(is)) {
            // 8KB 缓冲区
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = bis.read(buffer)) != -1) {
                md.update(buffer, 0, bytesRead);
            }
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        }
    }

}
